package com.zh.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zh.constants.Constants;
import com.zh.constants.ErrorCode;
import com.zh.mapper.PermissionMapper;
import com.zh.mapstruct.LoginStructMapper;
import com.zh.pojo.domain.Login;
import com.zh.mapper.LoginMapper;
import com.zh.pojo.dto.AdminLoginDto;
import com.zh.pojo.dto.WechatLoginDto;
import com.zh.pojo.result.LoginSuccess;
import com.zh.pojo.vo.LoginVo;
import com.zh.service.ILoginService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zh.template.WeChatTemplate;
import com.zh.utils.AssertUtil;
import com.zh.utils.NameUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;

/**
 * <p>
 * 登录表 服务实现类
 * </p>
 *
 * @author 初四
 * @since 2024-06-15
 */
@Service
@Slf4j
public class LoginServiceImpl extends ServiceImpl<LoginMapper, Login> implements ILoginService {

    @Autowired
    private WeChatTemplate weChatTemplate;

    @Autowired
    private PermissionMapper permissionMapper;
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Autowired
    private PasswordEncoder passwordEncoder;
    /**
     * 微信小程序登录
     *
     * @param loginDto 接收前端传递参数
     */
    @Override
    public LoginSuccess wechatLogin(WechatLoginDto loginDto) {
        // 1、获取OpenID
        String openId = weChatTemplate.getOpenId(loginDto.getCode());
        // 对openId进行断言判断
        AssertUtil.isNotEmpty(openId, "登录失败");
        // 2、 根据OpenId + 登录类型 查询数据库
        LambdaQueryWrapper<Login> loginQuery =
                new LambdaQueryWrapper<Login>().eq(Login::getOpenId, openId).eq(Login::getType, loginDto.getType());
        Login login = super.getOne(loginQuery);
        AssertUtil.isNotNull(login, "请先注册");

        // 3、执行登录
        LoginSuccess loginSuccess = execLogin(login);

        return loginSuccess;

    }

    /**
     * 客户端管理员登录
     *
     * @param adminLoginDto
     * @return
     */
    @Override
    public LoginSuccess adminLogin(AdminLoginDto adminLoginDto) {
        // 1、根据用户名查询列表
        Login login = super.getOne(new LambdaQueryWrapper<Login>().eq(Login::getUsername, adminLoginDto.getUsername()));
        // 断言判断 用户是否为空
        AssertUtil.isNotNull(login, "用户名或密码错误");
        // 2、比对密码是否正确
        boolean matches = passwordEncoder.matches(adminLoginDto.getPassword(), login.getPassword());
        AssertUtil.isTrue(matches, "用户名或密码错误");
        // 3、执行登录
        LoginSuccess loginSuccess = execLogin(login);
        return loginSuccess;
    }

    /**
     * 客户端管理员退出登录
     */
    @Override
    public void adminLoginOut() {
        // 1、获取当前会话Id
        long loginId = StpUtil.getLoginIdAsLong();// 获取当前会话账号id, 并转换为long类型
        // 2、删除redis 中缓存数据
        String permissionKey = String.format(Constants.Redis.PERMISSIONS_KEY, loginId);
        redisTemplate.delete(permissionKey);
        log.info("退出登录 - 删除权限列表{}", permissionKey);
        // 3、执行登录退出
        StpUtil.logout(loginId);
    }

    /**
     * 初始化(创建)管理员
     * ...初始化管理员,保存Login，密码使用 BCryptPasswordEncoder 加密...
     */
    @Override
    public void createAdmin() {
        // 根据初始化用户名 查询数据库 , 如果存在则不创建， 如果不存在，则保存login
        Login loginOne = super.getOne(new LambdaQueryWrapper<Login>().eq(Login::getUsername,
                Constants.Login.ADMIN_USERNAME));
        if(loginOne != null){
            return;
        }
        Login login = new Login();
        login.setUsername(Constants.Login.ADMIN_USERNAME);
        // 将密码进行加密
        String encode = passwordEncoder.encode(Constants.Login.ADMIN_PASSWORD);
        login.setPassword(encode);
        login.setType(Constants.Login.TYPE_ADMIN);
        login.setEnabled(true);
        login.setAvatar(Constants.Login.AVATAR_ADDRESS);
        login.setAdmin(true);
        login.setNickName(NameUtil.getAdminName());
        login.setName(Constants.Login.ADMIN_NAME);
        login.setCreateTime(new Date());
        // 保存login
        super.save(login);
    }


    /**
     * 执行登录，并向前端返回数据： 如token , 用户信息（头像，昵称，电话号码等）//TODO
     *
     * @param login
     */
    private LoginSuccess execLogin(Login login) {
        // 1、登录
        StpUtil.login(login.getId());

        // 2、获取satoken 获取当前会话的 token 值
        String satoken = StpUtil.getTokenValue();
        log.info("satoken 值:{}", satoken);


        // 查询 用户的权限列表， 并存入redis 中， 方便后续 做权限认证，从redis中读取
        List<String> permissionValues = permissionMapper.selectByOpenId(login.getId());
        String permissionKey = String.format(Constants.Redis.PERMISSIONS_KEY, login.getId());
        // 存入redis 中
        redisTemplate.opsForValue().set(permissionKey, permissionValues);
        // 3、封装用户信息如：头像，昵称等返回给前端
        // 将login对象数据拷贝到 LoginVo
        LoginVo loginVo = LoginStructMapper.INSTANCE.loginVoConvertLogin(login);

        // 将用互信息缓存到redis
        String loginInfoKey = String.format(Constants.Redis.LOGIN_INFO_KEY, login.getId());
        redisTemplate.opsForValue().set(loginInfoKey,loginVo);
        log.info("将用户信息缓存到redis:{}",loginInfoKey);

        LoginSuccess loginSuccess = new LoginSuccess();
        loginSuccess.setLogin(loginVo);
        loginSuccess.setSatoken(satoken);
        loginSuccess.setPermissions(permissionValues);
        return loginSuccess;
    }
}
